<?php
/** Yz_SQL_Transaction
 *
 *  In fact, locking is not a replacement for transactions!
 *
 *  currently, the syntax is:
 *  $t = Yz_SQL_Transaction::create()
 *      ->lockRead(tables)
 *      ->lockWrite(tables)
 *      ->execute();
 *  $t->commit();
 *
 *  @todo TODO implement selection between LOCK and BEGIN
 *
 */

class Yz_SQL_Transaction
    extends Yz_Class
    implements Yz_SQL_Statement_Interface
{

    const LOCK_READ = 'READ';
    const LOCK_WRITE = 'WRITE';

    /** the table names and lock types
     *
     *  array of strings "table-name lock-type"
     *  @var array
     */
    protected $tables = array();

    /**
     *  @var Yz_SQL_Adapter_Interface&
     */
    protected $db;


    /**
     *  Whether the transaction/lock has been started
     *  @var bool
     */
    protected $inTransaction = false;

    /**
    *   create transaction
    *
    *   @param ???
    *   @return self
    */
    final public static function create( $connectionName=null )
    {
        debug();

        $conn = Yz_SQL_Adapter::selectConnection($connectionName);

        return new static( $conn );
    } // create



    /** __construct()
    *
    *
    */
    protected function __construct( $connection )
    {
        debug();

        $this->db = $connection;
    } // __construct



    /** lock the tables with the specified lock type
    *
    *   If a table name is specified multiple times, the last specification takes precedence
    *   @return chainable
    */
    protected function lock( $tables, $lockType )
    {
        debug();
        $t = Yz_String::valueOf($tables);
        if (null === $t) {
            $t = new Yz_Array($tables);
        } else {
            $t = array($t);
        }
        foreach ($t as $tn) {
            // TODO validate table name
            $this->tables[$tn] = $tn . ' ' . $lockType;
        }
        return $this;
    } // lock()

    /** lock the tables with read lock
    *
    *   @param array|string $tables
    *   @see lock()
    *   @return chainable
    */
    public function lockRead( $tables )
    {
        return $this->lock($tables, self::LOCK_READ);
    } // lockRead()

    /** lock the tables with write lock
    *
    *   @param array|string $tables
    *   @see lock()
    *   @return chainable
    */
    public function lockWrite( $tables )
    {
        return $this->lock($tables, self::LOCK_WRITE);
    } // lockWrite()



    /** TODO must be a possibility to execute query as prepared stmt
    *
    *   @return <this>
    */
    public function execute()
    {
        debug();

        $this->testInTransaction();
        $this->doLock();
        // TODO select between transaction and lock

        $this->inTransaction = true;
        return $this;
    } // execute



    /** commit transaction
    *
    *   @return <this>
    */
    public function commit()
    {
        debug();

        if (! $this->inTransaction) {
            throw StateError::_new_();
            // TODO correct Ex type
            //throw error("Not in transaction");
        }
        $this->unlock();
        return $this;
    } // commit




    protected function testInTransaction()
    {
        if ( $this->inTransaction ) {
            throw StateError::_new_();
            // TODO correct Ex type
            //throw error("Already in transaction");
        }
        return $this;
    }


    /**
     *  set the table lock
     *  @return chainable
     *  @throw Yz_SQL_Error
     */
    protected function doLock()
    {
        debug();
        if (0 === count($this->tables)) {
            throw StateError::_new_();
            // TODO correct Ex type
            //throw ("No tables");
        }
        $sql = "LOCK TABLES\n  " . join(",\n  ", $this->tables);
        $this->db->execute( $sql );
        return $this;
    } // doLock


    /**
     *  remove the table lock
     *  @return chainable
     *  @throw Yz_SQL_Error
     */
    protected function unlock()
    {
        debug();
        $sql = "UNLOCK TABLES";
        $this->db->execute( $sql );
        return $this;
    } // unlock


} // class

